package checkers.oigj;

import java.util.List;
import javax.annotation.processing.*;
import javax.lang.model.type.*;
import javax.lang.model.util.*;
import javax.lang.model.element.*;

import com.sun.source.tree.*;

import checkers.source.*;
import checkers.basetype.*;
import checkers.oigj.quals.*;
import checkers.quals.TypeQualifiers;
import checkers.types.*;
import checkers.types.AnnotatedTypeMirror.AnnotatedDeclaredType;
import checkers.util.*;

public class OIGJVisitor extends BaseTypeVisitor<Void, Void> {

    /** The various OIGJ annotations. */
    private final AnnotationMirror READONLY, IMMUTABLE, OI, BOTTOM_QUAL,
	ASSIGNS_FIELDS, OMUTABLE, PRIVATEI, PRIVATEMUTABLE, PUBLICI, PUBLICMUTABLE;

    public OIGJVisitor(OIGJChecker checker, CompilationUnitTree root) {
        super(checker, root);

        READONLY = checker.READONLY;
        IMMUTABLE = checker.IMMUTABLE;
        OI = checker.OI;
        BOTTOM_QUAL = checker.BOTTOM_QUAL;
        ASSIGNS_FIELDS = checker.ASSIGNS_FIELDS;
        OMUTABLE = checker.OMUTABLE;
        PRIVATEI = checker.PRIVATEI;
        PRIVATEMUTABLE = checker.PRIVATEMUTABLE;
        PUBLICI = checker.PUBLICI;
        PUBLICMUTABLE = checker.PUBLICMUTABLE;
    }

    @Override
    public Void visitAssignment(AssignmentTree node, Void p) {

	// variable = expression
	
	ExpressionTree rhs = node.getExpression();
	Element rhsSym = InternalUtils.symbol(rhs);
	TypeMirror type = InternalUtils.typeOf(rhs);

	if (rhsSym != null) {
	    if (rhsSym.getAnnotation(PrivateI.class) != null) {

		/*
		System.out.println("OIGJ DEBUG: Checking " + node + " with rhs = " + rhs +
				   " and rhsSym = " + rhsSym + " and type = " + type);
		*/
		
		if (!TreeUtils.isSelfAccess(rhs)) {
		    checker.report(Result.failure(rhs + " is owned by @PrivateI and thus" +
						  " has to be accessed using this.", rhs),
				   rhs);
		}
	    }
	}
	
	ExpressionTree lhs = node.getVariable();
	Element lhsSym = InternalUtils.symbol(lhs);
	type = InternalUtils.typeOf(lhs);

	if (lhsSym != null) {
	    if (lhsSym.getAnnotation(PrivateI.class) != null) {

		/*
		System.out.println("OIGJ DEBUG: Checking " + node + " with lhs = " + lhs +
				   " and lhsSym = " + lhsSym + " and type = " + type);
		*/
		
		if (!TreeUtils.isSelfAccess(lhs)) {
		    checker.report(Result.failure(lhs + " is owned by @PrivateI and thus" +
						  " has to be accessed using this.", lhs),
				   lhs);
		}
	    }
	}
	
	return super.visitAssignment(node, p);
    }

    @Override
    public Void visitMethodInvocation(MethodInvocationTree node, Void p) {

	/*
	System.out.println("OIGJ DEBUG: Visiting method invocation " + node);
	*/

	// Get the annotations on the formal parameters (as declared).
	Element methodElement = InternalUtils.symbol(node);
	if (methodElement instanceof ExecutableElement) {
	    ExecutableElement ee = (ExecutableElement) methodElement;
	    List<? extends VariableElement> params = ee.getParameters();
	    for (VariableElement ve : params) {
		if (ve.getAnnotation(PrivateI.class) != null) {
		    if (!TreeUtils.isSelfAccess(node)) {

			// Also allow access via outer class's this!
			if (node.toString().contains(".this.")) {
			    /*
			    System.out.println("OIGJ DEBUG: Processing node " + node);
			    System.out.println("OIGJ DEBUG: I assume this is access " +
					       "via the outer class's this.");
			    */
			} else {

			    checker.report(Result.failure(node + " has an argument owned by " +
							  "@PrivateI and thus has to be " +
							  "accessed by this.", node),
					   node);
			    
			}
		    }
		}
	    }
	}

	/*
	// NB! Actual argument annotations are not relevant.
	List<? extends ExpressionTree> args = node.getArguments();
	for (ExpressionTree arg : args) {
	    Element argSym = InternalUtils.symbol(arg);
	    TypeMirror type = InternalUtils.typeOf(arg);
	    if (argSym != null) {
		if (argSym.getAnnotation(PrivateI.class) != null) {
		    if (!TreeUtils.isSelfAccess(node)) {
			checker.report(Result.failure(node + " has an argument owned by " +
						      "@PrivateI and thus has to be " +
						      "accessed by this.", node),
				       node);
		    }
		}
	    }
	}
	*/

	return super.visitMethodInvocation(node, p);
    }
}
